/**
 * 
 */
package xiaojian.toolkit.transport.codec;

import java.util.List;
import java.util.UUID;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoder;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import xiaojian.toolkit.bean.bytebean.core.BeanFieldCodec;
import xiaojian.toolkit.bean.tlv.TLVSignal;
import xiaojian.toolkit.bean.tlv.annotation.TLVAttribute;
import xiaojian.toolkit.bean.tlv.encode.TLVEncodeContext;
import xiaojian.toolkit.bean.tlv.encode.TLVEncoderOfBean;
import xiaojian.toolkit.bean.xip.annotation.SsipSignal;
import xiaojian.toolkit.bean.xip.core.XipSignal;
import xiaojian.toolkit.transport.protocol.xip.XipHeader;
import xiaojian.toolkit.util.ByteUtils;

/**
 * @author isdom
 * 
 */
public class MinaMixedEncoder implements ProtocolEncoder {

	private static final Logger logger = LoggerFactory
			.getLogger(MinaMixedEncoder.class);
	private int dumpBytes = 256;
	private boolean isDebugEnabled;
	private BeanFieldCodec xipBeanCodec;
	private TLVEncoderOfBean tlvBeanEncoder;

	/**
	 * @return the tlvBeanEncoder
	 */
	public TLVEncoderOfBean getTlvBeanEncoder() {
		return tlvBeanEncoder;
	}

	/**
	 * @param tlvBeanEncoder
	 *            the tlvBeanEncoder to set
	 */
	public void setTlvBeanEncoder(TLVEncoderOfBean tlvBeanEncoder) {
		this.tlvBeanEncoder = tlvBeanEncoder;
	}

	/**
	 * @return the xipBeanCodec
	 */
	public BeanFieldCodec getXipBeanCodec() {
		return xipBeanCodec;
	}

	/**
	 * @param xipBeanCodec
	 *            the xipBeanCodec to set
	 */
	public void setXipBeanCodec(BeanFieldCodec xipBeanCodec) {
		this.xipBeanCodec = xipBeanCodec;
	}

	private XipHeader createHeader(byte basicVer, UUID id, int messageCode,
			int messageLen) {

		XipHeader header = new XipHeader();

		header.setTransaction(id);

		int headerSize = xipBeanCodec.getStaticByteSize(XipHeader.class);

		header.setLength(headerSize + messageLen);
		header.setMessageLength(messageLen);
		header.setMessageCode(messageCode);
		header.setBasicVer(basicVer);

		return header;
	}

	private byte[] encodeXip(XipSignal signal) throws Exception {
		// once
		byte[] bytesBody = xipBeanCodec.encode(xipBeanCodec
				.getEncContextFactory().createEncContext(signal,
						signal.getClass(), null));

		SsipSignal attr = signal.getClass().getAnnotation(SsipSignal.class);
		if (null == attr) {
			throw new RuntimeException(
					"invalid ssip signal, bcs of no messageCode.");
		}

		XipHeader header = createHeader((byte) 1, signal.getIdentification(),
				attr.messageCode(), bytesBody.length);

		header.setTypeForClass(signal.getClass());

		byte[] bytes = ArrayUtils.addAll(xipBeanCodec.encode(xipBeanCodec
				.getEncContextFactory().createEncContext(header,
						XipHeader.class, null)), bytesBody);

		if (logger.isDebugEnabled() && isDebugEnabled) {
			logger.debug("encode XipMessage:" + signal);
			logger.debug("and XipMessage raw bytes -->");
			logger.debug(ByteUtils.bytesAsHexString(bytes, dumpBytes));
		}

		return bytes;
	}

	private byte[] encodeTLV(TLVSignal signal) throws Exception {

		TLVEncodeContext ctx = tlvBeanEncoder.getEncodeContextFactory()
				.createEncodeContext(signal.getClass(), null);
		List<byte[]> byteList = tlvBeanEncoder.encode(signal, ctx);

		TLVAttribute attr = signal.getClass().getAnnotation(TLVAttribute.class);
		if (null == attr) {
			throw new RuntimeException(
					"invalid top tlv object, missing @TLVAttribute.");
		}

		byte[] bytesBody = ByteUtils.union(byteList);
		XipHeader header = createHeader((byte) 2, signal.getIdentification(),
				attr.tag(), bytesBody.length);

		header.setSourceId(signal.getSourceId());
		header.setTypeForClass(signal.getClass());

		byte[] bytes = ArrayUtils.addAll(xipBeanCodec.encode(xipBeanCodec
				.getEncContextFactory().createEncContext(header,
						XipHeader.class, null)), bytesBody);

		if (logger.isDebugEnabled() && isDebugEnabled) {
			logger.debug("encode TLV:" + signal);
			logger.debug("and TLV raw bytes -->");
			logger.debug(ByteUtils.bytesAsHexString(bytes, dumpBytes));
		}

		return bytes;
	}

	/**
	 * @see org.apache.mina.filter.codec.ProtocolEncoder#encode(org.apache.mina.core.session.IoSession,
	 *      java.lang.Object,
	 *      org.apache.mina.filter.codec.ProtocolEncoderOutput)
	 */
	public void encode(IoSession session, Object message,
			ProtocolEncoderOutput out) throws Exception {

		byte[] bytes = null;
		if (message instanceof XipSignal) {
			bytes = encodeXip((XipSignal) message);
		} else if (message instanceof TLVSignal) {
			bytes = encodeTLV((TLVSignal) message);
		} else {
			throw new RuntimeException("encode: bean " + message
					+ " is not neigher XipSignal nor TLVSignal.");
		}
		if (null != bytes) {
			if (logger.isDebugEnabled()) {
				logger.debug("bean type {} and encode size is {}",
						message.getClass(), bytes.length);
			}
			out.write(IoBuffer.wrap(bytes));
		} else {
			logger.error("encode: " + message
					+ " can not generate byte stream.");
		}
	}

	public boolean isDebugEnabled() {
		return isDebugEnabled;
	}

	public void setDebugEnabled(boolean isDebugEnabled) {
		this.isDebugEnabled = isDebugEnabled;
	}

	public int getDumpBytes() {
		return dumpBytes;
	}

	/**
	 * @param dumpBytes
	 *            the dumpBytes to set
	 */
	public void setDumpBytes(int dumpBytes) {
		this.dumpBytes = dumpBytes;
	}

	public void dispose(IoSession session) throws Exception {
	}
}
